#include <stdint.h>
#include <stdio.h>
#include <set>
// includes the right splitlock version
#include "pwext.hpp"
#include <stdio.h>
#include <unistd.h>

pw::semaphore ready(0);

class worker : public pw::pwthread
{
	friend class splitlock_set;
public:
	// DEBUG {
	worker() :
		split_(true), sem_(0), stop_(false), steps_(0)
	{
		start();
	}
	~worker()
	{
		join();
	}
	struct request {
		int op;
	};
	bool get_next_request_nowait(request *r)
	{
		return false;
	}
	void get_next_request(request *r)
	{
	}
	// DEBUG }
	// ...
	void *execute()
	{
		bool done = false;
		split_.set_worker_intent(pw::splitlock::WORKER_WILL_ENTER);
		// ... initialize ...
	// DEBUG {
		ready.signal();
	// DEBUG }
		while(!done)
		{
			// intent is always WILL_ENTER at this point

			request req;
			// get the next request
			if (!get_next_request_nowait(&req))  {
				// no request available immediately, 
				// and no way to tell when it will be
				// (depending on the usage, WORKER_MIGHT_ENTER could be a
				// better choice here)
				split_.set_worker_intent(pw::splitlock::WORKER_WONT_ENTER);
				get_next_request(&req);
				// split_.enter() will set it implicitly, but
				// the explicit setting is more reliable
				split_.set_worker_intent(pw::splitlock::WORKER_WILL_ENTER);
			}

	// DEBUG {
			sem_.wait();
	// DEBUG }
			split_.enter();
			// ... process the request ...
	// DEBUG {
			++steps_;
			fprintf(stderr, "worker %p entered, total %d\n", this, steps_);
			done = stop_;
	// DEBUG }
			if (done)
				split_.set_worker_intent(pw::splitlock::WORKER_WONT_ENTER);
			else
				split_.set_worker_intent(pw::splitlock::WORKER_WILL_ENTER);
			split_.leave();
		}
		return 0;
	}

public:
	pw::splitlock split_;
public:
	pw::semaphore sem_;
	bool stop_;
	int steps_;
};

// [[ex03ja]]
// DEBUG descr The set of split locks with a common control API.
class splitlock_set
{
public:
	typedef std::set<worker *> worker_set;

	// Methods that potentially may be called by any
	// thread

	// implicitly locks the worker set if it wasn't
	// locked
	void request_safe()
	{
		pw::lockmutex lm(cond_);
		lset_ = true;
		rsafe_ = true;
		worker_set::iterator it;
		for (it = workers_.begin(); 
				it != workers_.end(); ++ it)
			(*it)->split_.request_safe();
		for (it = new_workers_.begin(); 
				it != new_workers_.end(); ++ it)
			(*it)->split_.request_safe();
	}

	void add_thread(worker *w)
	{
		w->split_.lock_safe(); // outside the mutex

		pw::lockmutex lm(cond_);
		if (lset_) {
			new_workers_.insert(w);
		} else {
			workers_.insert(w);
		}
		if (!rsafe_)
			w->split_.release_safe();
	}

	// Methods that potentially may be called by any
	// thread except the control

	// must not be called by control thread
	void rm_thread(worker *w) 
	{
		worker_set::iterator it;

		pw::lockmutex lm(cond_);

		it = new_workers_.find(w);
		if (it != new_workers_.end()) {
			// threads may be removed from the new subset
			// at any time
			new_workers_.erase(it);
			return;
		}

		while (lset_) 
			cond_.wait();

		it = workers_.find(w);
		if (it != workers_.end()) {
			workers_.erase(it);
		} else {
			// check again, just in case
			it = new_workers_.find(w);
			if (it != new_workers_.end()) {
				new_workers_.erase(it);
			}
		}
	}

	// Methods that may be called only by the control
	// thread

	splitlock_set() :
		lset_(false), rsafe_(false)
	{ }

	// before destruction, its up to the control
	// thread to decide, what to do with the workers:
	// make sure that they've exited, or somehow tell
	// them that they're not in the set any more (if
	// they care)
	~splitlock_set()
	{ }

	// implicitly locks the worker set if it wasn't
	// locked
	void wait_safe()
	{
		lock_set();

		// must leave the mutex unlocked, the lock on
		// the set is enough!
		worker_set::iterator it;
		for (it = workers_.begin(); 
				it != workers_.end(); ++ it)
			(*it)->split_.wait_safe();
	}

	// A convenience combination
	void lock_safe()
	{
		request_safe();
		wait_safe();
	}

	// implicitly unlocks the worker set
	void release_safe()
	{
		// outside of the mutex, avoid the deadlock
		unlock_set(); 

		pw::lockmutex lm(cond_);
		rsafe_ = false;
		worker_set::iterator it;
		for (it = workers_.begin(); 
				it != workers_.end(); ++ it)
			(*it)->split_.release_safe();
	}

	// for added threads, don't require safety
	void set_add_unsafe()
	{
		pw::lockmutex lm(cond_);
		rsafe_ = false;
		worker_set::iterator it;
		for (it = new_workers_.begin(); 
				it != new_workers_.end(); ++ it)
			(*it)->split_.release_safe();
	}

	// for added threads, require safety
	void set_add_safe()
	{
		pw::lockmutex lm(cond_);
		rsafe_ = true;
		worker_set::iterator it;
		for (it = new_workers_.begin(); 
				it != new_workers_.end(); ++ it)
			(*it)->split_.request_safe();
		// the safety of the new workers is requested
		// but not achieved yet
	}

	void lock_set()
	{
		pw::lockmutex lm(cond_);
		lset_ = true;
	}

	void unlock_set()
	{
		pw::lockmutex lm(cond_);
		lset_ = false;
		// collect the threads from the new subset
		worker_set::iterator it;
		for (it = new_workers_.begin(); 
				it != new_workers_.end(); ++ it) {
			workers_.insert(*it);
			if (!rsafe_)
				(*it)->split_.release_safe();
		}
		new_workers_.clear();
		cond_.broadcast();
	}

	// To be called by the control thread only,
	// bypasses the set lock.
	void control_rm_thread(worker *w)
	{
		worker_set::iterator it;

		pw::lockmutex lm(cond_);

		it = workers_.find(w);
		if (it != workers_.end()) {
			workers_.erase(it);
		} else {
			it = new_workers_.find(w);
			if (it != new_workers_.end()) {
				new_workers_.erase(it);
			}
		}
	}

	// Wait until all workers try to enter the
	// critical section, or if it's unknown whether
	// some of them will, then wait no longer than
	// until the specified time.
	// May be called only when the set is in safe
	// condition, and locked.  Returns true if the
	// wait succeeded, false if timed out (i.e. if
	// there are any workers that are left not in the
	// critical section)
	// @param abstime - time limit 
	// @param timed - if false, the time limit applies
	//   only if it's unknown whether the worker will
	//   enter, if true, the time limit applies even if
	//   it's known that the worker will enter
	bool wait_for_workers(const struct timespec &abstime, 
		bool timed = false)
	{
		bool res = true;

		worker_set::iterator it;
		for (it = workers_.begin(); 
				it != workers_.end(); ++ it)
			res = (res && (*it)->split_.
				wait_for_worker(abstime, timed));

		return res;
	}

	// Lets the control thread iterate and search on
	// the set.  The set must be locked, for safe
	// handling.
	const worker_set &get_set()
	{
		return workers_;
	}

protected:

	worker_set workers_;
	// where the workers are added when the set is
	// locked
	worker_set new_workers_; 
	// signaled on unlocking of the set
	pw::pmcond cond_; 
	// flag: safety of all workers requested, and set
	// has been locked
	bool lset_; 
	// flag: safety of all workers requested
	bool rsafe_; 
};
// END

class ex03j_2 : public splitlock_set
{
public:
void ex03jb()
{
// [[ex03jb]]
	unlock_set();
	lock_set();
// END
}

void ex03jc()
{
// [[ex03jc]]
	request_safe(); // sometime before
	wait_safe();
	// ...
	set_add_unsafe();
	// ...
	// DEBUG {
	worker w4;
	ready.wait(1);
	w4.split_.release_safe();
	add_thread(&w4);
	// DEBUG }
	unlock_set();
	lock_set(); // now may have unsafe threads on the set!
	// DEBUG {
	print_wset(get_set());
	w4.sem_.signal(1);
	usleep(100*1000);
	// DEBUG }
	request_safe(); // ensure of the safety again
	wait_safe();
	// DEBUG {
	w4.stop_ = true;
	w4.sem_.signal(1);
	usleep(100*1000);
	fprintf(stderr, "removing the thread\n");
	control_rm_thread(&w4);
	print_wset(get_set());
	w4.split_.release_safe();
	// DEBUG }
// END
}

void ex03jd()
{
// [[ex03jd]]
	request_safe(); // sometime before
	wait_safe();
	// ...
	// DEBUG {
	worker w4;
	ready.wait(1);
	w4.split_.release_safe();
	add_thread(&w4);
	// DEBUG }
	set_add_unsafe();
	set_add_safe();
	// ...
	unlock_set();
	lock_set(); // now may have unsafe threads on the set!
	// DEBUG {
	w4.sem_.signal(1);
	usleep(100*1000);
	print_wset(get_set());
	w4.sem_.signal(1);
	// DEBUG }
	wait_safe(); // ensure of the safety again
	// DEBUG {
	w4.stop_ = true;
	w4.sem_.signal(1);
	usleep(100*1000);
	fprintf(stderr, "removing the thread\n");
	control_rm_thread(&w4);
	print_wset(get_set());
	w4.split_.release_safe();
	// DEBUG }
// END
}

// DEBUG {
class deleter : public pw::pwthread
{
public:
	deleter(splitlock_set *where, worker *what) :
		where_(where), what_(what)
	{ 
		start();
	}
	~deleter()
	{
		join();
	}
	void * execute()
	{
		where_->rm_thread(what_);
		fprintf(stderr, "removed %p\n", what_);
		return 0;
	}
	splitlock_set *where_;
	worker *what_;
};

static void print_wset(const splitlock_set::worker_set &s)
{
	fprintf(stderr, "set contains:");
	for (splitlock_set::worker_set::const_iterator it = s.begin(); it != s.end(); ++it)
		fprintf(stderr, " %p", *it);
	fprintf(stderr, "\n");
}

static void main()
{
	worker w1, w2;
	ready.wait(2);
	ex03j_2 slock;
	splitlock_set::worker_set wset;

	slock.add_thread(&w1);
	w1.sem_.signal(1);
	usleep(100*1000);
	slock.request_safe();
	slock.wait_safe();
	print_wset(slock.get_set());

	fprintf(stderr, "- ex03jb\n");
	slock.add_thread(&w2);
	print_wset(slock.get_set());
	slock.ex03jb();
	print_wset(slock.get_set());

	fprintf(stderr, "- ex03jc\n");
	slock.ex03jc();

	fprintf(stderr, "- ex03jd\n");
	slock.ex03jd();

	fprintf(stderr, "- deletion 1\n");
	slock.release_safe();
	{
		deleter dl(&slock, &w1);
	}
	slock.add_thread(&w1);

	fprintf(stderr, "- deletion 2\n");
	slock.request_safe();
	slock.wait_safe();
	{
		deleter dl(&slock, &w1);
		usleep(100*1000);
		fprintf(stderr, "may only delete after this\n");
		slock.unlock_set();
	}
	slock.add_thread(&w1);

	// guaranteed stopping
	fprintf(stderr, "- stop\n");
	slock.release_safe();
	w1.stop_ = true;
	w1.sem_.signal(100);
	w2.stop_ = true;
	w2.sem_.signal(100);
}
// DEBUG }
};

// DEBUG {
int main()
{
	ex03j_2::main();
	return 0;
}
// DEBUG }
/* Sample output:

worker 0x7fff95e6a4d0 entered, total 1
set contains: 0x7fff95e6a4d0
- ex03jb
set contains: 0x7fff95e6a4d0
set contains: 0x7fff95e6a2f0 0x7fff95e6a4d0
- ex03jc
set contains: 0x7fff95e6a0f0 0x7fff95e6a2f0 0x7fff95e6a4d0
worker 0x7fff95e6a0f0 entered, total 1
removing the thread
set contains: 0x7fff95e6a2f0 0x7fff95e6a4d0
worker 0x7fff95e6a0f0 entered, total 2
- ex03jd
set contains: 0x7fff95e6a0f0 0x7fff95e6a2f0 0x7fff95e6a4d0
removing the thread
set contains: 0x7fff95e6a2f0 0x7fff95e6a4d0
worker 0x7fff95e6a0f0 entered, total 1
- deletion 1
removed 0x7fff95e6a4d0
- deletion 2
may only delete after this
removed 0x7fff95e6a4d0
- stop
worker 0x7fff95e6a2f0 entered, total 1
worker 0x7fff95e6a4d0 entered, total 2

*/
